<script setup>
import { ref, watch, onMounted, onUpdated, toRaw } from "vue";
import _ from "loadsh";
import * as uuid from "uuid";
import * as p from "./data/props";
import * as draw from "./data/draw";
import * as extra from "./data/extraData";
import DefaultNode from "./default/node.vue";
import DefaultKnot from "./default/knot.vue";

const props = defineProps(p.validProps);

const zoomListner = ref(null);
const transform = ref("");
const scale = ref(1);
const changedSize = ref(false);
const treeData = ref({});
const nodeClass = ref(`node-${uuid.v4().replaceAll("-", "")}`);

const foreignObjectStyle = ref({
  overflow: 'visible'
})

const nodeStyle = ref({
  position: 'absolute'
})

//树节点
const nodes = ref([]);
//连接线源节点
const stretchLines = ref([]);
//横向直线连接线
const straightHLines = ref([]);
//竖向直线连接线
const straightVLines = ref([]);
//曲线连接线
const curveLines = ref([]);
//连接节点
const knots = ref([]);
//连接点延长线
const knotStretchLines = ref([]);

const treeRef = ref(null);

onMounted(() => {
  treeData.value = _.cloneDeep(props.data);
  const listner = draw.zoomListner(props, treeRef.value, (e) => {
    transform.value = e.transform;
    scale.value = e.transform.k;
  });
  zoomListner.value = listner;
  _reDraw();
});

onUpdated(() => {
  if (!changedSize.value) {
    _reSize();
    changedSize.value = true;
  }
});

watch(
  () => props.data,
  (data) => {
    treeData.value = _.cloneDeep(data);
    changedSize.value = false;
    _reDraw();
  }
);

watch(
  () => props.showKnot,
  () => {
    changedSize.value = false;
    _reDraw();
  }
);

/**
 * 重绘
 */
function _reDraw() {
  if (treeData.value && Object.keys(treeData.value).length > 0) {
    const data = draw.draw(treeData.value, props);
    nodes.value = data.nodes;
    stretchLines.value = data.stretchLines;
    straightHLines.value = data.straightHLines;
    straightVLines.value = data.straightVLines;
    curveLines.value = data.curveLines;
    knots.value = data.knots;
    knotStretchLines.value = data.knotStretchLines;
  }
}

/**
 * 调整节点大小
 */
function _reSize() {
  draw.reSize(nodes.value, scale.value, toRaw(nodeClass.value));
  _reDraw();
}

/**
 * 默认节点的标题
 * @param {*} data
 * @param {*} index
 */
function _getDefaultNodeTitle(data, index) {
  return p.getDefaultNodeKey(props, data) || index;
}

/**
 * 默认值节点样式
 * @param {*} node
 */
function _getDefaultNodeStyle(node) {
  if (extra.getNodeRootCollapsed(node)) {
    return p.getDefaultNodeCollapsedStyle(props, node);
  } else {
    return p.getDefaultNodeStyle(props, node);
  }
}

/**
 * 点击树节点
 */
function _clickTreeNode(node) {
  if (p.isClickTreeNodeCollapsedWay(props)) {
    const collapsed = extra.getNodeRootCollapsed(node);
    collapseNode(node, !collapsed);
  }
}

/**
 * 点击连接节点
 */
function _clickKnotNode(node) {
  if (p.isClickKnotNodeCollapsedWay(props)) {
    const collapsed = extra.getNodeRootCollapsed(node);
    collapseNode(node, !collapsed);
  }
}

/**
 * 展开或折叠节点
 * @param {z*} node
 * @param {*} collapsed
 */
function collapseNode(node, collapsed) {
  draw.collapseNode(node, collapsed);
  _reDraw();
}

/**
 * 缩放
 * @param {*} ratio
 */
function zoom(ratio) {
  draw.zoom(treeRef.value, toRaw(zoomListner.value), ratio);
}

defineExpose({
  collapseNode,
  zoom,
});
</script>

<template>
  <div 
    id="tree-container" 
    ref="treeRef"
    :style="{
      width: '100%',
      height: '100%'
    }"
  >
    <svg width="100%" height="100%">
      <g :transform="transform">
        <!-- nodes -->
        <foreignObject
          v-for="(item, index) in nodes"
          :key="index"
          :x="item.x - item.xSize / 2"
          :y="item.y"
          :width="item.xSize"
          :style="foreignObjectStyle"
          height="1"
          ref="root"
        >
          <div
            :style="nodeStyle"
            class="node-container"
            :class="nodeClass"
            :data-node-id="item.id"
            @click="() => _clickTreeNode(item)"
          >
            <slot name="node" :data="item.data" :index="index" :node="item">
              <DefaultNode
                :title="_getDefaultNodeTitle(item.data, index + 1)"
                :style="_getDefaultNodeStyle(item)"
              />
            </slot>
          </div>
        </foreignObject>
        <!-- lines -->
        <g
          :stroke="lineStyle.stroke"
          :stroke-width="lineStyle.strokeWidth"
          :stroke-opacity="lineStyle.strokeOpacity"
          :stroke-dasharray="lineStyle.strokeDasharray"
          fill="none"
        >
          <!-- stretch lines -->
          <path
            v-for="(node, index) in stretchLines"
            :key="`stretch-link-${index}`"
            :d="draw.drawStretchLine(node, props)"
            stroke-linecap="butt"
          />
          <!-- straight lines -->
          <g v-if="p.isStraightLink(props)">
            <path
              v-for="(node, index) in straightHLines"
              :key="`h-straight-link-${index}`"
              :d="draw.drawHStraightLine(node, props)"
              stroke-linecap="square"
            />
            <path
              v-for="(link, index) in straightVLines"
              :key="`h-straight-link-${index}`"
              :d="draw.drawVStraightLine(link, props)"
              stroke-linecap="butt"
            />
          </g>
          <!-- curve lines -->
          <g v-if="p.isCurveLink(props)">
            <path
              v-for="(link, index) in curveLines"
              :key="`curve-link-${index}`"
              :d="draw.drawCurveLink(link, props)"
              stroke-linecap="butt"
            />
          </g>
        </g>
        <g v-if="showKnot">
          <!-- knot stretch lines -->
          <path
            v-for="(node, index) in knotStretchLines"
            :key="`knot-stretch-line-${index}`"
            :d="draw.drawKnotStretchLine(node, props)"
            :stroke="lineStyle.stroke"
            :stroke-width="lineStyle.strokeWidth"
            :stroke-opacity="lineStyle.strokeOpacity"
            :stroke-dasharray="lineStyle.strokeDasharray"
            stroke-linecap="butt"
            fill="none"
          />
          <foreignObject
            v-for="(node, index) in knots"
            :key="index"
            :x="draw.calcKnotX(node, props)"
            :y="draw.calcKnotY(node, props)"
            :width="extra.getKnotWidth(node)"
            :height="1"
            :style="foreignObjectStyle"
          >
            <div 
              :style="nodeStyle"
              class="knot-container"
              :data-node-id="node.id"
              @click="() => _clickKnotNode(node)">
              <slot name="knot" :data="node.data" :node="node" :index="index">
                <DefaultKnot
                  :num="node.numChildren"
                  :collapsed="extra.getNodeRootCollapsed(node)"
                />
              </slot>
            </div>
          </foreignObject>
        </g>
      </g>
    </svg>
  </div>
</template>
